// Copyright Gary Evans 2006-2007.

using System;
using System.Collections.Generic;
using System.Text;

using NUnit.Framework;

using Taumuon.Jabuka.Physics.Collision;

namespace Taumuon.Jabuka.Physics.Test
{
    /// <summary>
    /// Tests the operation of the CollisionManager class.
    /// </summary>
    [TestFixture]
    public class TestCollisionManager
    {
        /// <summary>
        /// Checks that the CollisionManager checks its arguments.
        /// </summary>
        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void TestConstructCollisionManagerNullList()
        {
            CollisionManager manager = new CollisionManager(null);
        }

		/// <summary>
		/// Tests that the collision manager clones its objects
		/// </summary>
		[Test]
		public void TestCollisionManagerClones()
		{
			MockEulerRigidBody mockEulerRigidBody1 = new MockEulerRigidBody();
			MockEulerRigidBody mockEulerRigidBody2 = new MockEulerRigidBody();
			MockEulerRigidBody mockEulerRigidBodyClone1 = new MockEulerRigidBody();
			MockEulerRigidBody mockEulerRigidBodyClone2 = new MockEulerRigidBody();
            MockFactoryClonesSpecified<MockEulerRigidBody> cloneFactory1 = 
                new MockFactoryClonesSpecified<MockEulerRigidBody>();
            cloneFactory1.MockClone = mockEulerRigidBodyClone1;
			mockEulerRigidBody1.CloneFactory = cloneFactory1;
            MockFactoryClonesSpecified<MockEulerRigidBody> cloneFactory2 = 
                new MockFactoryClonesSpecified<MockEulerRigidBody>();
            cloneFactory2.MockClone = mockEulerRigidBodyClone2;
			mockEulerRigidBody2.CloneFactory = cloneFactory2;

			Sphere sphere1 = new Sphere(mockEulerRigidBody1, 1.0);
			Sphere sphere2 = new Sphere(mockEulerRigidBody2, 1.0);
			List<Sphere> spheres = new List<Sphere>();
			spheres.Add(sphere1);
			spheres.Add(sphere2);
			Assert.IsFalse(mockEulerRigidBody1.UpdateCalled);
			Assert.IsFalse(mockEulerRigidBody2.UpdateCalled);

			CollisionManager manager = new CollisionManager(spheres);

			// Check that constructor doesn't update.
			Assert.IsFalse(mockEulerRigidBody1.UpdateCalled, "Constructor shouldn't update");
			Assert.IsFalse(mockEulerRigidBody2.UpdateCalled, "Constructor shouldn't update");

			// Check that constructor doesn't clone.
			Assert.IsFalse(mockEulerRigidBody1.CloneCalled, "Constructor shouldn't clone");
			Assert.IsFalse(mockEulerRigidBody2.CloneCalled, "Constructor shouldn't clone");

			manager.Update(0.1);

			// Check that the original objects have been cloned.
			Assert.IsTrue(mockEulerRigidBody1.CloneCalled, "Originals should be cloned");
			Assert.IsTrue(mockEulerRigidBody2.CloneCalled, "Originals should be cloned");

			// Check that the original objects haven't been updated.
			Assert.IsFalse(mockEulerRigidBody1.UpdateCalled, "Originals should be updated");
			Assert.IsFalse(mockEulerRigidBody2.UpdateCalled, "Originals should be updated");
		}

        /// <summary>
        /// Tests that the collision manager calls update
        ///  on all of its cloned objects.
        /// </summary>
        [Test]
        public void TestCollisionManagerCallsUpdate()
        {
			MockEulerRigidBody mockEulerRigidBody1 = new MockEulerRigidBody();
			MockEulerRigidBody mockEulerRigidBody2 = new MockEulerRigidBody();
			MockEulerRigidBody mockEulerRigidBodyClone1 = new MockEulerRigidBody();
			MockEulerRigidBody mockEulerRigidBodyClone2 = new MockEulerRigidBody();
            MockFactoryClonesSpecified<MockEulerRigidBody> cloneFactory1 = 
                new MockFactoryClonesSpecified<MockEulerRigidBody>();
            cloneFactory1.MockClone = mockEulerRigidBodyClone1;
            mockEulerRigidBody1.CloneFactory = cloneFactory1;
            MockFactoryClonesSpecified<MockEulerRigidBody> cloneFactory2 =
                new MockFactoryClonesSpecified<MockEulerRigidBody>();
            cloneFactory2.MockClone = mockEulerRigidBodyClone2;
            mockEulerRigidBody2.CloneFactory = cloneFactory2;

			Sphere sphere1 = new Sphere(mockEulerRigidBody1, 1.0);
			Sphere sphere2 = new Sphere(mockEulerRigidBody2, 1.0);
			List<Sphere> spheres = new List<Sphere>();
			spheres.Add(sphere1);
			spheres.Add(sphere2);
			Assert.IsFalse( mockEulerRigidBody1.UpdateCalled );
			Assert.IsFalse( mockEulerRigidBody2.UpdateCalled );

			CollisionManager manager = new CollisionManager(spheres);

			// Check that constructor doesn't update.
			Assert.IsFalse(mockEulerRigidBody1.UpdateCalled);
			Assert.IsFalse(mockEulerRigidBody2.UpdateCalled);

			// Check that constructor doesn't update clones.
			Assert.IsFalse(mockEulerRigidBodyClone1.UpdateCalled);
			Assert.IsFalse(mockEulerRigidBodyClone2.UpdateCalled);

			manager.Update(0.1);

			// Check that originals haven't been updated.
			Assert.IsFalse(mockEulerRigidBody1.UpdateCalled);
			Assert.IsFalse(mockEulerRigidBody2.UpdateCalled);

			// Check that the clones have been updated.
			Assert.IsTrue(mockEulerRigidBodyClone1.UpdateCalled);
			Assert.IsTrue(mockEulerRigidBodyClone2.UpdateCalled);
        }

		/// <summary>
		/// Tests that CheckForCollision is called against all possible
		///  sphere-sphere collisions, and that it isn't called more than
		///  once for any sphere-sphere collision.
		/// </summary>
		[Test]
		public void TestCollisionManagerCallsCheckForCollisionAgainstSpheres()
		{
			MockEulerRigidBody mockEulerRigidBody1 = new MockEulerRigidBody();
			MockEulerRigidBody mockEulerRigidBody2 = new MockEulerRigidBody();
			MockEulerRigidBody mockEulerRigidBody3 = new MockEulerRigidBody();
			MockEulerRigidBody mockEulerRigidBodyClone1 = new MockEulerRigidBody();
			MockEulerRigidBody mockEulerRigidBodyClone2 = new MockEulerRigidBody();
			MockEulerRigidBody mockEulerRigidBodyClone3 = new MockEulerRigidBody();
            MockFactoryClonesSpecified<MockEulerRigidBody> cloneFactory1 = 
                new MockFactoryClonesSpecified<MockEulerRigidBody>();
            cloneFactory1.MockClone = mockEulerRigidBodyClone1;
            mockEulerRigidBody1.CloneFactory = cloneFactory1;
            MockFactoryClonesSpecified<MockEulerRigidBody> cloneFactory2 = 
                new MockFactoryClonesSpecified<MockEulerRigidBody>();
            cloneFactory2.MockClone = mockEulerRigidBodyClone2;
            mockEulerRigidBody2.CloneFactory = cloneFactory2;
            MockFactoryClonesSpecified<MockEulerRigidBody> cloneFactory3 = 
                new MockFactoryClonesSpecified<MockEulerRigidBody>();
            cloneFactory3.MockClone = mockEulerRigidBodyClone2;
            mockEulerRigidBody3.CloneFactory = cloneFactory3;

			MockSphere mockSphere1 = new MockSphere(mockEulerRigidBody1);
			MockSphere mockSphere2 = new MockSphere(mockEulerRigidBody2);
			MockSphere mockSphere3 = new MockSphere(mockEulerRigidBody3);
			MockSphere mockSphereClone1 = new MockSphere(mockEulerRigidBodyClone1);
			MockSphere mockSphereClone2 = new MockSphere(mockEulerRigidBodyClone2);
			MockSphere mockSphereClone3 = new MockSphere(mockEulerRigidBodyClone3);
            MockFactoryClonesSpecified<MockSphere> sphereCloneFactory1 = 
                new MockFactoryClonesSpecified<MockSphere>();
            sphereCloneFactory1.MockClone = mockSphereClone1;
			mockSphere1.MockSphereCloneFactory = sphereCloneFactory1;
            MockFactoryClonesSpecified<MockSphere> sphereCloneFactory2 = 
                new MockFactoryClonesSpecified<MockSphere>();
            sphereCloneFactory2.MockClone = mockSphereClone2;
            mockSphere2.MockSphereCloneFactory = sphereCloneFactory2;
            MockFactoryClonesSpecified<MockSphere> sphereCloneFactory3 =
                new MockFactoryClonesSpecified<MockSphere>();
            sphereCloneFactory3.MockClone = mockSphereClone3;
            mockSphere3.MockSphereCloneFactory = sphereCloneFactory3;

			List<Sphere> mockSpheres = new List<Sphere>();
			mockSpheres.Add(mockSphere1);
			mockSpheres.Add(mockSphere2);
			mockSpheres.Add(mockSphere3);

			CollisionManager manager = new CollisionManager(mockSpheres);

			manager.Update(0.1);

			// NOTE: these throw an exception if the check fails.
			mockSphere1.ConfirmCollidedWith(new MockSphere[] {});
			mockSphere2.ConfirmCollidedWith(new MockSphere[] {});
			mockSphere3.ConfirmCollidedWith(new MockSphere[] {});

			// NOTE: these throw an exception if the check fails.
			mockSphereClone1.ConfirmCollidedWith(new MockSphere[] { mockSphereClone2, mockSphereClone3 });
			mockSphereClone2.ConfirmCollidedWith(new MockSphere[] { mockSphereClone3 });
			mockSphereClone3.ConfirmCollidedWith(new MockSphere[] { });
		}

		/// <summary>
		/// Checks that the parameters of the original objects are updated if there are no collisions.
		/// </summary>
		[Test]
		public void TestCollisionManagerCommitsChangesWithNoCollisions()
		{
			// NOTE: The mockEulerRigidBody returns the clone below, none of its original settings
			//  are cloned, so just create it with the default constructor.
			MockEulerRigidBody mockEulerRigidBody1 = new MockEulerRigidBody();
			MockEulerRigidBody mockEulerRigidBody2 = new MockEulerRigidBody();
			MockEulerRigidBody mockEulerRigidBodyClone1 = new MockEulerRigidBody(new Vector(),
				new Vector(1.0, 1.0, 1.0), new Vector(), 1.0);
			MockEulerRigidBody mockEulerRigidBodyClone2 = new MockEulerRigidBody(new Vector(2.0, 2.0, 2.0),
				new Vector(-1.0, -1.0, -1.0), new Vector(), 1.0);
			MockSphere mockSphere1 = new MockSphere(mockEulerRigidBody1);
			MockSphere mockSphere2 = new MockSphere(mockEulerRigidBody2);
			MockSphere mockSphereClone1 = new MockSphere(mockEulerRigidBodyClone1);
			MockSphere mockSphereClone2 = new MockSphere(mockEulerRigidBodyClone2);

            MockFactoryClonesSpecified<MockSphere> sphereCloneFactory1 = 
                new MockFactoryClonesSpecified<MockSphere>();
            sphereCloneFactory1.MockClone = mockSphereClone1;
            mockSphere1.MockSphereCloneFactory = sphereCloneFactory1;
            MockFactoryClonesSpecified<MockSphere> sphereCloneFactory2 = 
                new MockFactoryClonesSpecified<MockSphere>();
            sphereCloneFactory2.MockClone = mockSphereClone2;
            mockSphere2.MockSphereCloneFactory = sphereCloneFactory2;

			List<Sphere> spheres = new List<Sphere>();
			spheres.Add(mockSphere1);
			spheres.Add(mockSphere2);

			CollisionManager manager = new CollisionManager(spheres);

			manager.Update(0.1);

			Assert.AreEqual(1, mockSphere1.SpheresSetSphereCalledWith.Count);
			Assert.IsTrue(object.ReferenceEquals(mockSphere1.SpheresSetSphereCalledWith[0],
				mockSphereClone1));
			Assert.IsNotNull(mockSphere2.SpheresSetSphereCalledWith);
			Assert.IsTrue(object.ReferenceEquals(mockSphere2.SpheresSetSphereCalledWith[0],
				mockSphereClone2));

			Assert.IsTrue(TestVector.AreVectorValuesCorrect(mockEulerRigidBody1.Velocity,
				1.0, 1.0, 1.0));
			Assert.IsTrue(TestVector.AreVectorValuesCorrect(mockEulerRigidBody2.Velocity,
				-1.0, -1.0, -1.0));
		}

		/// <summary>
		/// Tests that for any collisions detected, the objects involved have their
		///  properties updated.
		/// </summary>
		[Test]
		public void TestCollisionManagerPerformsCollisionResponseOnCollisions()
		{
			// NOTE: The mockEulerRigidBody returns the clone below, none of its original settings
			//  are cloned, so just create it with the default constructor.
			MockEulerRigidBody mockEulerRigidBody1 = new MockEulerRigidBody();
			MockEulerRigidBody mockEulerRigidBody2 = new MockEulerRigidBody();
			MockEulerRigidBody mockEulerRigidBodyClone1 = new MockEulerRigidBody(new Vector(),
				new Vector(1.0, 1.0, 1.0), new Vector(), 1.0);
			MockEulerRigidBody mockEulerRigidBodyClone2 = new MockEulerRigidBody(new Vector(2.0, 2.0, 2.0),
				new Vector(-1.0, -1.0, -1.0), new Vector(), 1.0);
			MockSphere mockSphere1 = new MockSphere(mockEulerRigidBody1);
			MockSphere mockSphere2 = new MockSphere(mockEulerRigidBody2);
			MockSphere mockSphereClone1 = new MockSphere(mockEulerRigidBodyClone1);
			MockSphere mockSphereClone2 = new MockSphere(mockEulerRigidBodyClone2);

            MockFactoryClonesSpecified<MockSphere> sphereCloneFactory1 = 
                new MockFactoryClonesSpecified<MockSphere>();
            sphereCloneFactory1.MockClone = mockSphereClone1;
            mockSphere1.MockSphereCloneFactory = sphereCloneFactory1;
            MockFactoryClonesSpecified<MockSphere> sphereCloneFactory2 = 
                new MockFactoryClonesSpecified<MockSphere>();
            sphereCloneFactory2.MockClone = mockSphereClone2;
            mockSphere2.MockSphereCloneFactory = sphereCloneFactory2;

			List<Sphere> spheres = new List<Sphere>();
			spheres.Add(mockSphere1);
			spheres.Add(mockSphere2);

			// Don't want to make any assumptions in the test code here about which
			//  objects will actually have their collisions tested against.
			mockSphereClone1.CheckForCollisionResult = CollisionType.Collision;
			mockSphereClone2.CheckForCollisionResult = CollisionType.Collision;

			CollisionManager manager = new CollisionManager(spheres);

			manager.Update(0.1);

			Assert.AreEqual(1, mockSphere1.SpheresSetSphereCalledWith.Count);
			Assert.IsTrue(object.ReferenceEquals(mockSphere1.SpheresSetSphereCalledWith[0],
				mockSphereClone1));
			Assert.IsNotNull(mockSphere2.SpheresSetSphereCalledWith[0]);
			Assert.IsTrue(object.ReferenceEquals(mockSphere2.SpheresSetSphereCalledWith[0],
				mockSphereClone2));

			Assert.IsFalse(TestVector.AreVectorValuesCorrect(mockEulerRigidBody1.Velocity,
				1.0, 1.0, 1.0));
			Assert.IsFalse(TestVector.AreVectorValuesCorrect(mockEulerRigidBody2.Velocity,
				-1.0, -1.0, -1.0));
		}

        /// <summary>
        /// Tests that the CollisionManager.Update() method iterates to find the
        ///  time of collision when the collison occurs within the overall time interval.
        /// </summary>
        [Test]
        public void TestIteratesToTimeOfCollision0625()
        {
            MockEulerRigidBody mockEulerRigidBody1 = new MockEulerRigidBody(
                new Vector(0.0, 0.0, 0.0),
                new Vector(1.0, 0.0, 0.0),
                new Vector(1.0, 0.0, 0.0),
                1.0);
            MockEulerRigidBody mockEulerRigidBody2 = new MockEulerRigidBody(
                new Vector(1.0, 0.0, 0.0),
                new Vector(-1.0, 0.0, 0.0),
                new Vector(-1.0, 0.0, 0.0),
                1.0);
            MockSphereIterations mockSphere1 = new MockSphereIterations(mockEulerRigidBody1, 0.625);
            MockSphereIterations mockSphere2 = new MockSphereIterations(mockEulerRigidBody2, 0.625);

            mockEulerRigidBody1.CloneFactory = new MockEulerRigidBodyFactoryIteration(mockEulerRigidBody1);
            mockEulerRigidBody2.CloneFactory = new MockEulerRigidBodyFactoryIteration(mockEulerRigidBody2);

            mockSphere1.MockSphereCloneFactory = new MockSphereFactoryIteration(mockEulerRigidBody1, 0.625);
            mockSphere2.MockSphereCloneFactory = new MockSphereFactoryIteration(mockEulerRigidBody2, 0.625);

            List<Sphere> spheres = new List<Sphere>();
            spheres.Add(mockSphere1);
            spheres.Add(mockSphere2);

            CollisionManager manager = new CollisionManager(spheres);

            manager.Update(1.0);

            Assert.AreEqual(2, mockSphere1.SpheresSetSphereCalledWith.Count, "should have set spheres twice");
            Assert.AreEqual(2, mockSphere2.SpheresSetSphereCalledWith.Count, "should have set spheres twice");
            Assert.AreEqual(1, ((MockEulerRigidBody)mockSphere1.SpheresSetSphereCalledWith[0].EulerRigidBody).UpdateTimes.Count,
                "Should only have one update time");
            Assert.AreEqual(1, ((MockEulerRigidBody)mockSphere2.SpheresSetSphereCalledWith[0].EulerRigidBody).UpdateTimes.Count,
                "Should only have one update time");
            double sphere1Time1 = ((MockEulerRigidBody)mockSphere1.SpheresSetSphereCalledWith[0].EulerRigidBody).UpdateTimes[0];
            double sphere1Time2 = ((MockEulerRigidBody)mockSphere1.SpheresSetSphereCalledWith[1].EulerRigidBody).UpdateTimes[0];
            double sphere2Time1 = ((MockEulerRigidBody)mockSphere2.SpheresSetSphereCalledWith[0].EulerRigidBody).UpdateTimes[0];
            double sphere2Time2 = ((MockEulerRigidBody)mockSphere2.SpheresSetSphereCalledWith[1].EulerRigidBody).UpdateTimes[0];

            Assert.AreEqual(0.625, sphere1Time1, "First update should be at 0.625");
            Assert.AreEqual(0.625, sphere2Time1, "First update should be at 0.625");
            Assert.AreEqual(0.375, sphere1Time2, "First update should be at 0.625");
            Assert.AreEqual(0.375, sphere2Time2, "First update should be at 0.625");
        }
    }
}
